using System.Collections;

namespace NFreeMarker.Template
{
    /**
     * A simple implementation of {@link TemplateCollectionModel}.
     * It's able to wrap <tt>java.util.Iterator</tt>-s and <tt>java.util.Collection</tt>-s.
     * If you wrap an <tt>Iterator</tt>, the variable can be &lt;list>-ed (&lt;forach>-ed) only once!
     *
     * <p>Consider using {@link SimpleSequence} instead of this class if you want to wrap <tt>Iterator</tt>s.
     * <tt>SimpleSequence</tt> will read all elements of the <tt>Iterator</tt>, and store them in a <tt>List</tt>
     * (this may cause too high resource consumption in some applications), so you can list the variable
     * for unlimited times. Also, if you want to wrap <tt>Collection</tt>s, and then list the resulting
     * variable for many times, <tt>SimpleSequence</tt> may gives better performance, as the
     * wrapping of non-<tt>TemplateModel</tt> objects happens only once.
     *
     * <p>This class is thread-safe. The returned <tt>TemplateModelIterator</tt>-s
     * are <em>not</em> thread-safe.
     *
     * @version $Id: SimpleCollection.java,v 1.13 2004/11/27 14:49:57 ddekany Exp $
     */

    public class SimpleCollection : WrappingTemplateModel, ITemplateCollectionModel // TODO: , Serializable
    {
        private bool _iteratorDirty;
        private readonly IEnumerator _iterator;
        private readonly IEnumerable _collection;

        public SimpleCollection(IEnumerator iterator)
        {
            _iterator = iterator;
        }

        public SimpleCollection(IEnumerable collection)
        {
            _collection = collection;
        }

        public SimpleCollection(IEnumerator iterator, IObjectWrapper wrapper)
            : base(wrapper)
        {
            _iterator = iterator;
        }

        public SimpleCollection(IEnumerable collection, IObjectWrapper wrapper)
            : base(wrapper)
        {
            _collection = collection;
        }

        /**
         * Retrieves a template model iterator that is used to iterate over the elements in this collection.
         *  
         * <p>When you wrap an <tt>Iterator</tt> and you get <tt>TemplateModelIterator</tt> for multiple times,
         * only on of the returned <tt>TemplateModelIterator</tt> instances can be really used. When you have called a
         * method of a <tt>TemplateModelIterator</tt> instance, all other instance will throw a
         * <tt>TemplateModelException</tt> when you try to call their methods, since the wrapped <tt>Iterator</tt>
         * can't return the first element.
         */

        public ITemplateModelIterator Iterator()
        {
            if (_iterator != null)
            {
                return new SimpleTemplateModelIterator(this, _iterator, true);
            }

            lock (_collection)
            {
                return new SimpleTemplateModelIterator(this, _collection.GetEnumerator(), false);
            }
        }

        /*
         * An instance of this class must be accessed only from a single thread.
         * The encapsulated Iterator may accessible from multiple threads (as multiple
         * SimpleTemplateModelIterator instance can wrap the same Iterator instance),
         * but the first thread which uses the shared Iterator will monopolize that.
         */

        private class SimpleTemplateModelIterator : ITemplateModelIterator
        {
            private readonly SimpleCollection _collection;
            private readonly IEnumerator _iterator;
            private bool _iteratorShared;
            private bool _hasNext;

            internal SimpleTemplateModelIterator(SimpleCollection collection, IEnumerator iterator, bool iteratorShared)
            {
                _collection = collection;
                _iterator = iterator;
                _iteratorShared = iteratorShared;
                _hasNext = iterator.MoveNext();
            }

            public ITemplateModel Next()
            {
                if (_iteratorShared) MakeIteratorDirty();

                if (!HasNext())
                {
                    throw new TemplateModelException("The collection has no more elements.");
                }

                object value = _iterator.Current;
                _hasNext = _iterator.MoveNext();

                if (value is ITemplateModel)
                {
                    return (ITemplateModel) value;
                }

                return _collection.Wrap(value);
            }

            public bool HasNext()
            {
                /* 
                 * Theorically this should not make the iterator dirty,
                 * but I met sync. problems if I don't do it here. :(
                 */
                if (_iteratorShared) MakeIteratorDirty();
                return _hasNext;
            }

            private void MakeIteratorDirty()
            {
                lock (_collection)
                {
                    if (_collection._iteratorDirty)
                    {
                        throw new TemplateModelException(
                            "This collection variable wraps a java.util.Iterator, "
                            + "thus it can be <list>-ed or <foreach>-ed only once");
                    }

                    _collection._iteratorDirty = true;
                    _iteratorShared = false;
                }
            }
        }
    }
}